﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Microsoft.Win32;
using System.IO;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Data;
using System.Collections;
using Kokomo.Environment.Commanding;
using Kokomo.Environment.Services;
using Kokomo.Controls;
using Kokomo.Environment.Hosting.Settings;
using Kokomo.Environment.Extensibility;

namespace Kokomo.Environment.Hosting
{
	/// <summary>
	/// Provides functionality as the viewmodel for <see cref="EditorApplication"/>.
	/// </summary>
	/// <remarks>
	/// This class implements the menu and toolbar commands.  It also tracks the
	/// </remarks>
	class EditorApplicationViewModel : RegionViewModel
	{
		/// <summary>
		/// Initializes a new instance of <see cref="EditorApplicationViewModel"/>.
		/// </summary>
		/// <param name="editorApplication">The <see cref="EditorApplication"/> to expose.</param>
		/// <exception cref="ArgumentNullException"><paramref name="editorApplication"/> is <c>null</c>.</exception>
		public EditorApplicationViewModel(EditorApplication editorApplication)
		{
			if (editorApplication == null) throw new ArgumentNullException("editorApplication");
			this.EditorApplication = editorApplication;

			this.DocumentManager = editorApplication.DocumentManager;
			this.DocumentManager.ActiveDocumentChanged += new EventHandler(DocumentManager_ActiveDocumentChanged);

			this.NewDocumentCommand = new ParameterizedCommand(this.CreateNewDocument);
			this.OpenDocumentCommand = new DelegatedCommand(this.EditorApplication.OpenDocument);
			this.OpenRecentDocumentCommand = new ParameterizedCommand(this.OpenRecentDocument);
			this.SaveDocumentCommand = new DelegatedCommand(this.SaveDocument, this.CanSaveActiveDocument);
			this.SaveDocumentAsCommand = new DelegatedCommand(this.SaveDocumentAs, this.CanSaveActiveDocument);
			this.SaveAllDocumentsCommand = new DelegatedCommand(this.SaveAllDocuments);
			this.ExitCommand = new DelegatedCommand(this.Exit);

			this.RegisterCommand(this.NewDocumentCommand, NewDocumentCommandName);
			this.RegisterCommand(this.OpenDocumentCommand, OpenDocumentCommandName);
			this.RegisterCommand(this.SaveDocumentCommand, SaveDocumentCommandName);
			this.RegisterCommand(this.SaveDocumentAsCommand, SaveDocumentAsCommandName);
			this.RegisterCommand(this.SaveAllDocumentsCommand, SaveAllDocumentsCommandName);
			//this.RegisterCommand(this.OpenSettingsDialogCommand, OpenSettingsDialogCommandName);

			this.DropBehavior = new FileDropBehavior(editorApplication);

			const int menuGroupCount = 6;
			CollectionContainer[] menuGroups = new CollectionContainer[menuGroupCount];
			CompositeCollection mainMenuItems = new CompositeCollection();
			for (int i = 0; i < menuGroupCount; i++)
			{
				CollectionContainer menuGroup = new CollectionContainer();
				menuGroups[i] = menuGroup;
				mainMenuItems.Add(menuGroup);
			}
			this.MainMenuItems = mainMenuItems;
			this._menuGroups = menuGroups;

			IImageProvider images = editorApplication.ImageManager;

			bool hasTemplates = this.EditorApplication.DocumentTemplateManager.Templates.Count > 0;
			MenuItemViewModel newMenuItem = hasTemplates
				? (MenuItemViewModel)new NewDocumentMenuViewModel(this.NewDocumentCommand, this.EditorApplication.DocumentTemplateManager.Templates)
				: new MenuCommandItemViewModel(new CommandViewModel(this.NewDocumentCommand, UiString.FromString(Messages.NewDocumentCommandText), images.GetImage(StandardImageKeys.NewDocument)))
				;
			this.FileMenuItems = new MenuItemViewModel[] {
				new MenuViewModel(
					UiString.FromString(Messages.FileMenuTitle),
					new MenuItemViewModel[] {
						newMenuItem,
						null,
						new MenuCommandItemViewModel(new CommandViewModel(this.OpenDocumentCommand, UiString.FromString(Messages.OpenDocumentCommandText), images.GetImage(StandardImageKeys.OpenDocument))),
						new MenuCommandItemViewModel(new CommandViewModel(this.SaveDocumentCommand, UiString.FromString(Messages.SaveDocumentCommandText), images.GetImage(StandardImageKeys.SaveDocument))),
						new MenuCommandItemViewModel(new CommandViewModel(this.SaveDocumentAsCommand, UiString.FromString(Messages.SaveDocumentAsCommandText), null)),
						new MenuCommandItemViewModel(new CommandViewModel(this.SaveAllDocumentsCommand, UiString.FromString(Messages.SaveAllDocumentsCommandText), images.GetImage(StandardImageKeys.SaveAllDocuments))),
						null,
						new RecentDocumentMenuViewModel(this.OpenRecentDocumentCommand, editorApplication.RecentDocumentService.RecentDocuments),
						null,
						new MenuCommandItemViewModel(new CommandViewModel(this.ExitCommand, UiString.FromString(Messages.ExitCommandText), null)),
					})
				};

			this.ShowOptionsCommand = new DelegatedCommand(this.ShowOptions);
			List<MenuItemViewModel> toolMenuItems = new List<MenuItemViewModel>();
			toolMenuItems.Add(new MenuCommandItemViewModel(new CommandViewModel(this.ShowOptionsCommand, UiString.FromString(Messages.OptionsMenuText))));

			IEnumerable<ITool> tools = editorApplication.Tools.ToArray();
			if ((tools != null) && (tools.Count() > 0))
			{
				toolMenuItems.Add(null);
				foreach (var tool in tools)
				{
					CommandViewModel toolCommand = new CommandViewModel(
						new DelegatedCommand(tool.Activate),
						tool.Name,
						tool.IconSource
						);
					toolMenuItems.Add(new MenuCommandItemViewModel(toolCommand));
				}
			}

			IImageProvider imageProvider = EnvironmentServices.DemandService<IImageProvider>();

			this._openNewViewCommand = new DelegatedCommand(this.OpenNewView, this.CanOpenNewView);
			if (toolMenuItems.Count > 0)
			{
				this.WindowMenuItems = new MenuItemViewModel[] {
					new MenuViewModel(
						UiString.FromString(Messages.ToolsMenuTitle),
						toolMenuItems
						),
					new MenuViewModel(
						UiString.FromString(Messages.WindowMenuTitle),
						new MenuItemViewModel[] {
							new MenuCommandItemViewModel(new CommandViewModel(
								this.OpenNewViewCommand,
								UiString.FromString(Messages.OpenNewViewMenuText),
								imageProvider.GetImage(StandardImageKeys.NewWindow)
								))
						})
				};
			}
		}

		/// <summary>
		/// Gets the instance of <see cref="Hosting.EditorApplication"/>.
		/// </summary>
		public EditorApplication EditorApplication { get; private set; }

		public FileDropBehavior DropBehavior { get; private set; }

		#region Services
		/// <summary>
		/// Gets the instance of <see cref="IDocumentManager"/>.
		/// </summary>
		/// <remarks>
		/// This member is public to allow direct binding from the view.
		/// </remarks>
		public IDocumentManager DocumentManager { get; private set; }
		#endregion

		#region Menus
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the main menu.
		/// </summary>
		/// <remarks>
		/// This property returns a collection composed of the main menu groups:
		/// <list type="bulleted">
		/// <item><see cref="FileMenuItems"/></item>
		/// <item><see cref="EditMenuItems"/></item>
		/// <item><see cref="ViewMenuItems"/></item>
		/// <item><see cref="ObjectMenuItems"/></item>
		/// <item><see cref="WindowMenuItems"/></item>
		/// <item><see cref="HelpMenuItems"/></item>
		/// </list>
		/// </remarks>
		public IEnumerable MainMenuItems { get; private set; }

		private CollectionContainer[] _menuGroups;
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the File menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the application and is consistent among all documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable FileMenuItems
		{
			get { return this._menuGroups[0].Collection; }
			set { this._menuGroups[0].Collection = value; }
		}
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the Edit menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the editor and may vary among documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable EditMenuItems
		{
			get { return this._menuGroups[1].Collection; }
			set { this._menuGroups[1].Collection = value; }
		}
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the View menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the application and is consistent among all documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable ViewMenuItems
		{
			get { return this._menuGroups[2].Collection; }
			set { this._menuGroups[2].Collection = value; }
		}
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the Object menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the editor and may vary among documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable ObjectMenuItems
		{
			get { return this._menuGroups[3].Collection; }
			set { this._menuGroups[3].Collection = value; }
		}
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the Window menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the application and is consistent among all documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable WindowMenuItems
		{
			get { return this._menuGroups[4].Collection; }
			set { this._menuGroups[4].Collection = value; }
		}
		/// <summary>
		/// Gets the list of <see cref="MenuItem"/>s to display on the Help menu group.
		/// </summary>
		/// <remarks>
		/// This menu group is provided by the editor and may vary among documents.
		/// </remarks>
		/// <see cref="MainMenuItems"/>
		public IEnumerable HelpMenuItems
		{
			get { return this._menuGroups[5].Collection; }
			set { this._menuGroups[5].Collection = value; }
		}
		#endregion

		/// <summary>
		/// Gets a list of <see cref="DocumentViewModel"/>s that are visible in the main window.
		/// </summary>
		/// <remarks>
		/// This implementation gets the list from <see cref="IDocumentManager"/>.
		/// </remarks>
		public IEnumerable<DocumentViewModel> DocumentViewModels { get { return this.DocumentManager.DocumentViewModels; } }

		void ActiveDocumentViewModel_EditorMenuItemViewModelsChanged(object sender, EventArgs e)
		{
			this.EditMenuItems = this.DocumentManager.ActiveDocumentViewModel.EditorMenuItemViewModels;
		}

		void DocumentManager_ActiveDocumentChanged(object sender, EventArgs e)
		{
			DocumentViewModel activeDocumentViewModel = this.DocumentManager.ActiveDocumentViewModel;

			this._openNewViewCommand.OnCanExecuteChanged();

			if (activeDocumentViewModel == null)
			{
				this.EditMenuItems = null;
				this.ObjectMenuItems = null;
				this.HelpMenuItems = null;
			}
			else
			{
				this.EditMenuItems = activeDocumentViewModel.EditorMenuItemViewModels;
				this.ObjectMenuItems = activeDocumentViewModel.ObjectMenuItemViewModels;
				this.HelpMenuItems = activeDocumentViewModel.HelpMenuItemViewModels;
			}
		}

		/// <summary>
		/// Reopens the documents that were open from the previous session.
		/// </summary>
		/// <param name="dontCheckRecovery">Indicates whether to first check whether the last session ended abruptly.</param>
		/// <remarks>
		/// If <paramref name="dontCheckRecovery"/> is <c>false</c>, this method checks whether
		/// the last session ended abruptly, and if so, displays an error document.
		/// </remarks>
		public void ReopenDocumentsFromLastSession(bool dontCheckRecovery = false)
		{
			GeneralSettings generalSettings = this.EditorApplication.SettingsManager.GetSettings<GeneralSettings>();

			if (
				(generalSettings.ReloadOpenDocumentsAtStartup)
				&& (generalSettings.OpenDocumentList != null)
				)
			{
				DocumentViewModel lastDoc = null;
				if (!dontCheckRecovery && generalSettings.StartupRecovery)
				{
					ErrorDocumentViewModel errorDocViewModel = this.DocumentManager.CreateAndShowErrorDocumentViewModel(
						UiString.FromString(Messages.LastSessionAbortedMainCaption),
						UiString.FromString(Messages.LastSessionAbortedDetail)
						);
					errorDocViewModel.CommandViewModels.Add(this.CreateReopenCommand(errorDocViewModel));

					lastDoc = errorDocViewModel;
				}
				else
				{
					foreach (var fileName in generalSettings.OpenDocumentList)
					{
						if (File.Exists(fileName))
						{
							lastDoc = this.EditorApplication.OpenDocumentFromFile(fileName);
						}
					}
				}

				this.DocumentManager.ActiveDocumentViewModel = lastDoc;
			}

			if (
				generalSettings.CreateNewDocumentAtStartup
				&& (this.DocumentViewModels.Count() == 0)
				)
			{
				this.EditorApplication.CreateInitialDocument();
			}
		}
		private CommandViewModel CreateReopenCommand(
			DocumentViewModel documentViewModel
			)
		{
			if (documentViewModel == null) throw new ArgumentNullException("documentViewModel");

			return new CommandViewModel(new ParameterizedCommand(this.RetryReopenDocuments), UiString.FromString(Messages.ReopenDocumentsFromLastSessionText))
			{
				Argument = documentViewModel
			};
		}
		private void RetryReopenDocuments(object arg)
		{
			DocumentViewModel documentViewModel = arg as DocumentViewModel;

			this.ReopenDocumentsFromLastSession(true);
			if (documentViewModel != null) documentViewModel.Close();
		}

		#region Commands
		/// <summary>
		/// The name of the <c>New Document</c> command.
		/// </summary>
		public static readonly string NewDocumentCommandName = "NewDocument";
		/// <summary>
		/// The name of the <c>Open Document</c> command.
		/// </summary>
		public static readonly string OpenDocumentCommandName = "OpenDocument";
		/// <summary>
		/// The name of the <c>Save Document</c> command.
		/// </summary>
		public static readonly string SaveDocumentCommandName = "SaveDocument";
		/// <summary>
		/// The name of the <c>Save Document As</c> command.
		/// </summary>
		public static readonly string SaveDocumentAsCommandName = "SaveDocumentAs";
		/// <summary>
		/// The name of the <c>Save All Documents</c> command.
		/// </summary>
		public static readonly string SaveAllDocumentsCommandName = "SaveAllDocuments";
		/// <summary>
		/// The name of the <c>Open Settings Dialog</c> command.
		/// </summary>
		public static readonly string OpenSettingsDialogCommandName = "ShowSettings";

		#region File
		/// <summary>
		/// Gets the <c>New Document</c> command.
		/// </summary>
		public ICommand NewDocumentCommand { get; private set; }
		/// <summary>
		/// Creates a new document.
		/// </summary>
		/// <param name="arg">The <see cref="DocumentTemplate"/> to instantiate,
		/// or <c>null</c> for an empty document.</param>
		/// <remarks>
		/// If no <see cref="DocumentTemplate"/> is specified,
		/// this method calls <see cref="Hosting.EditorApplication.CreateNewDocument"/>
		/// </remarks>
		public void CreateNewDocument(object arg)
		{
			DocumentTemplate template = arg as DocumentTemplate;

			DocumentViewModel newDoc;
			if (template != null)
			{
				newDoc = template.Instantiate();
			}
			else
			{
				newDoc = this.EditorApplication.CreateNewDocument();
			}

			if (newDoc != null)
			{
				this.EditorApplication.DocumentManager.ShowDocument(newDoc);
				this.DocumentManager.ActiveDocumentViewModel = newDoc;
			}
		}

		/// <summary>
		/// Gets the <c>Open Document</c> command.
		/// </summary>
		public ICommand OpenDocumentCommand { get; private set; }

		/// <summary>
		/// Gets the <c>Save Document</c> command.
		/// </summary>
		public ICommand SaveDocumentCommand { get; private set; }
		void SaveDocument()
		{
			DocumentViewModel documentViewModel = this.DocumentManager.ActiveDocumentViewModel;
			if (documentViewModel == null) return;

			SaveDocument(documentViewModel);
		}

		bool CanSaveActiveDocument()
		{
			bool canSave =
				(this.DocumentManager.ActiveDocumentViewModel != null)
				&& (this.DocumentManager.ActiveDocumentViewModel.CanSave)
				;

			return canSave;
		}

		private void SaveDocument(DocumentViewModel documentViewModel)
		{
			if (documentViewModel == null) throw new ArgumentNullException("documentViewModel");

			if (documentViewModel.FileName == null)
			{
				this.SaveDocumentAs(documentViewModel);
			}
			else
			{
				documentViewModel.SaveTo(documentViewModel.FileName, true);
			}
		}

		/// <summary>
		/// Gets the <c>Save Document As</c> command.
		/// </summary>
		public ICommand SaveDocumentAsCommand { get; private set; }
		void SaveDocumentAs()
		{
			DocumentViewModel documentViewModel = this.DocumentManager.ActiveDocumentViewModel;
			if (documentViewModel == null) return;

			SaveDocumentAs(documentViewModel);
		}

		private void SaveDocumentAs(DocumentViewModel documentViewModel)
		{
			if (documentViewModel == null) throw new ArgumentNullException("documentViewModel");

			FileType[] possibleFileTypes = documentViewModel.PossibleFileTypes;
			string initialFileName = documentViewModel.FileName ?? documentViewModel.Title;

			string fileName = this.EditorApplication.UiService.ShowSaveFileDialog(new FileDialogOptions()
			{
				FileTypes = possibleFileTypes,
				Title = UiString.FromString(Messages.SaveDocumentTitle),
				InitialFileName = initialFileName
			});
			if (fileName == null) return;

			documentViewModel.SaveTo(fileName, true);
		}

		/// <summary>
		/// Gets the <c>Save All Documents</c> command.
		/// </summary>
		public ICommand SaveAllDocumentsCommand { get; private set; }
		void SaveAllDocuments()
		{
			foreach (var documentViewModel in this.DocumentViewModels)
			{
				if (documentViewModel.IsDirty)
				{
					this.SaveDocument(documentViewModel);
				}
			}
		}

		/// <summary>
		/// Gets the <c>Open Recent Document</c> command.
		/// </summary>
		/// <remarks>
		/// Specify the file name of the document as the argument to <see cref="ICommand.Execute"/>.
		/// </remarks>
		public ICommand OpenRecentDocumentCommand { get; private set; }
		void OpenRecentDocument(object arg)
		{
			string path = (string)arg;

			// Ensure file exists
			if (!File.Exists(path))
			{
				ErrorDocumentViewModel errorDoc = this.DocumentManager.CreateAndShowErrorDocumentViewModel(
					UiString.FromString(Messages.FileNotFoundText),
					UiString.FromString(Messages.FileNotFoundDetail)
					);

				this.DocumentManager.ShowDocument(errorDoc);

				return;
			}

			DocumentViewModel doc = this.EditorApplication.DocumentManager.OpenDocumentFromFile(path);
			if (doc != null)
			{
				this.EditorApplication.RecentDocumentService.OnOpenDocument(path);
				this.DocumentManager.ActiveDocumentViewModel = doc;
			}
		}

		/// <summary>
		/// Gets the <c>Exit</c> command.
		/// </summary>
		public ICommand ExitCommand { get; private set; }
		private void Exit()
		{
			this.EditorApplication.MainWindow.Close();
		}
		#endregion

		#region Tools
		private DocumentViewModel _optionsViewModel;
		public ICommand ShowOptionsCommand { get; private set; }
		private void ShowOptions()
		{
			if (this._optionsViewModel == null)
			{
				this._optionsViewModel = new OptionsViewModel();
			}
			this.DocumentManager.ShowDocument(this._optionsViewModel);
		}
		#endregion

		#region Window
		private DelegatedCommand _openNewViewCommand;
		public ICommand OpenNewViewCommand { get { return this._openNewViewCommand; } }
		private bool CanOpenNewView()
		{
			bool canOpen =
				(this.DocumentManager.ActiveDocumentViewModel != null)
				&& (this.DocumentManager.ActiveDocumentViewModel.CanShowMultipleViews)
				;
			return canOpen;
		}
		private void OpenNewView()
		{
			if (!this.CanOpenNewView()) return;

			this.DocumentManager.ActiveDocumentViewModel.OpenNewView();
		}
		#endregion

		#endregion
	}
}
